/* eslint-disable @typescript-eslint/no-this-alias */

import { useMessage } from '../../hooks'
import { addStyleToHead } from '../style'
import { getComAttrValue, getJSExpParams } from './attrs'
import { getTsToJs, getInsBwUtils } from './setupLogic'
import { isFunction, cloneDeep, isObject } from 'lodash-es'
import { h, markRaw, getCurrentInstance, defineComponent } from 'vue'
import { getCustomComPropDataType, getCustomComPropDefaultValue } from './props'

import {
  BwComTypeEnum,
  BwVueComNameEnum,
  BwCustomComFrontTypeEnum,
} from '../enums'

import {
  BwGetCustomComInfoFun,
  BwCallFunctionEngineFun,
  BwCustomComSoltTypeEnum,
  BwCustomComNodeIfFromTypeEnum as BwIfFromTypeEnum,
  BwCustomComNodeForFromTypeEnum as BwForFromTypeEnum,
  BwCustomComTrendSlotVarFromTypeEnum as BwSlotVarFromTypeEnum,
} from '../models'

import type { VNode, DefineComponent } from 'vue'
import type {
  BwVueIns,
  BwChildrenNodeCallback,
  BwCustomComSoltNodeInfo,
  BwCustomComSoltInfoModel,
  BwCustomComDesignClassConstructorModel,
} from './model'
import type {
  BwCustomComStructModel,
  BwCustomComDesignInfoModel,
  BwCustomComNodeIfInfoModel,
  BwCustomComNodeForInfoModel,
} from '../models'

import comEle from '../coms/comEle/index.vue'
import desiginItem from '../coms/nodeItem/index.vue'

import { getRenderComAttrs } from './util'

const { createWarningModal } = useMessage()

function hasCircularRef(obj: Record<string, any>) {
  const visited = new Set()
  function isCircular(obj: Record<string, any>) {
    if (visited.has(obj)) {
      return true
    }
    visited.add(obj)
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        if (typeof obj[key] === 'object' && obj[key] !== null) {
          if (isCircular(obj[key])) {
            return true
          }
        }
      }
    }
    return false
  }
  return isCircular(obj)
}

export class CustomComRender {
  protected curComNodes: Array<VNode> = []
  protected curVueIns: BwVueIns | null = null
  protected curCustomCom: DefineComponent | null = null

  protected curComCode: string | null = null
  protected frontType: BwCustomComFrontTypeEnum
  protected nodeForVars: Record<string, Array<any>> = {}
  protected designInfo: BwCustomComDesignInfoModel | null
  protected getCustomComInfo: BwGetCustomComInfoFun | null = null
  protected extendUtils: Record<string, any> = {}

  constructor(data: BwCustomComDesignClassConstructorModel) {
    this.designInfo = data.designInfo ?? null
    this.frontType = data.front || BwCustomComFrontTypeEnum.pc
    this.getCustomComInfo = data.getCustomComInfo
    this.extendUtils = data.extendUtils
  }

  /** 生成实例组件 */
  protected createCom() {
    const self = this
    const curCom = defineComponent({
      name: 'MyCustomComDesign',
      props: self.createComProps(),
      emits: self.createComEmits(),
      // @ts-ignore
      setup(props, { emit, expose }) {
        const curIns = getCurrentInstance()
        // @ts-ignore
        const curProxyIns: BwVueIns = curIns?.proxy as BwVueIns

        let resutl: Record<string, any> = {}
        let curSetupLogic = self.getSetupLogic()
        if (curSetupLogic) {
          curSetupLogic = getTsToJs(curSetupLogic)
          curSetupLogic = curSetupLogic.replace('export default', 'return ')
          const fn = new Function('bwUtils', curSetupLogic)
          const fnResutl = fn(
            getInsBwUtils(Object.assign({ params: {} }, self.extendUtils))
          )
          const setupFun = fnResutl.setup
          if (isFunction(setupFun)) {
            resutl = setupFun(props, emit, expose)
          }
        }

        return Object.assign(
          {
            bwSysGetComSlotInfoList() {
              const list = self.getComSlot(curProxyIns)
              return list
            },
          },
          resutl
        )
      },
      mounted() {
        self.mountComCss()
      },
      methods: {},
      render(this: BwVueIns) {
        self.curVueIns = this
        const nodes = self.initComNodes(this)

        if (Array.isArray(nodes) && nodes?.length) {
          if (nodes.length === 1) {
            return h(nodes[0], null)
          } else {
            return h(
              'div',
              {
                class: 'bw-custom-com-container',
                style: { display: 'inline-block' },
              },
              nodes
            )
          }
        } else {
          return h('div', {
            class: 'bw-custom-com-container',
            style: { display: 'inline-block' },
          })
        }
      },
    })
    return curCom
  }

  /** 生成实例组件外放属性 */
  protected createComProps() {
    const curProps: Record<string, any> = {}
    const comProps = this.designInfo?.comProps
    if (Array.isArray(comProps) && comProps.length) {
      comProps.forEach((n) => {
        const name = n.key
        const dataType = n.dataType
        if (name && Array.isArray(dataType) && dataType.length) {
          const { defaultValue, defaultValueDataType } = n
          curProps[name] = {
            require: !!n.isRequire,
            type: dataType.map((n) => getCustomComPropDataType(n)),
            default: getCustomComPropDefaultValue(
              defaultValueDataType ?? undefined,
              defaultValue ?? undefined
            ),
          }
        }
      })
    }
    return curProps
  }

  /** 生成实例组件外放事件 */
  protected createComEmits() {
    const emitList: Array<string> = []
    const emits = this.designInfo?.emits
    if (Array.isArray(emits) && emits.length) {
      emits.forEach((n) => emitList.push(n.key))
    }
    return emitList
  }

  /** 挂载全局样式 */
  protected mountComCss() {
    const curCss = this.designInfo?.comCss
    if (curCss) {
      const curFrontCss = curCss[this.frontType] ?? ''
      addStyleToHead(this.curComCode ?? 'my-com-crate-css', curFrontCss)
    }
  }

  /** 动态加载自定义逻辑 */
  protected getSetupLogic() {
    const curInfo = this.designInfo
    const curSetup = curInfo?.setup
    return curSetup ? curSetup[this.frontType] : undefined
  }

  /** 初始化组件节点 */
  protected initComNodes(vueIns: BwVueIns) {
    const structs = this.designInfo?.structs
    if (isObject(structs)) {
      const curFrontStructs = structs[this.frontType] ?? []
      const nodes = this.getNodeEleRenderInfo(vueIns, curFrontStructs, {})
      this.curComNodes = nodes
      return nodes
    } else {
      this.curComNodes = []
      return null
    }
  }

  /** 获取自定义组件插槽列表 */
  protected getComSlot(vueIns: BwVueIns) {
    const curInfo = this.designInfo
    const comSlots = curInfo?.comSlots

    const list: Array<BwCustomComSoltInfoModel> = []
    if (Array.isArray(comSlots) && comSlots.length) {
      comSlots.forEach((item) => {
        switch (item.slotType) {
          case BwCustomComSoltTypeEnum.convention: {
            const curInfo = item.conventionSlotInfo
            if (curInfo) {
              list.push({
                soltType: item.slotType,
                key: curInfo.slotCode,
                slotName: curInfo.slotCode,
                slotParams: curInfo.slotParams,
              })
            }
          }
          case BwCustomComSoltTypeEnum.trends: {
            const curInfo = item.trendsSlotInfo
            switch (curInfo?.trendVarFrom) {
              case BwSlotVarFromTypeEnum.comVar: {
                const data = vueIns[curInfo.comVarName ?? '']
                if (Array.isArray(data)) {
                  let curJsLogic = curInfo.slotCode
                  curJsLogic = getTsToJs(curJsLogic)
                  curJsLogic = curJsLogic.replace('export default', 'return ')
                  if (curJsLogic) {
                    const fn = new Function('bwUtils', curJsLogic)

                    const extndJSs = fn(vueIns)
                    if (isFunction(extndJSs?.extnd)) {
                      const slotNames = extndJSs.extnd(data)
                      if (Array.isArray(slotNames) && slotNames.length) {
                        slotNames.forEach((n) => {
                          list.push({
                            soltType: item.slotType,
                            key: curInfo.logicCode,
                            slotName: n,
                            slotParams: curInfo.slotParams,
                          })
                        })
                      }
                    }
                  }
                } else {
                  console.warn(
                    `动态插槽【${curInfo.logicCode}】的变量不是数组类型，请检查！`
                  )
                }
              }
              case BwSlotVarFromTypeEnum.comProps: {
                const curProps = vueIns.$props
                const data = curProps[curInfo.comPorpName ?? '']

                if (Array.isArray(data)) {
                  let curJsLogic = curInfo.slotCode
                  curJsLogic = getTsToJs(curJsLogic)
                  curJsLogic = curJsLogic.replace('export default', 'return ')
                  if (curJsLogic) {
                    const fn = new Function('bwUtils', curJsLogic)

                    const extndJSs = fn(vueIns)
                    if (isFunction(extndJSs?.extnd)) {
                      const slotNames = extndJSs.extnd(data)
                      if (Array.isArray(slotNames) && slotNames.length) {
                        slotNames.forEach((n) => {
                          list.push({
                            soltType: item.slotType,
                            key: curInfo.logicCode,
                            slotName: n,
                            slotParams: curInfo.slotParams,
                          })
                        })
                      }
                    }
                  }
                } else {
                  console.warn(
                    `动态插槽【${curInfo.logicCode}】的变量不是数组类型，请检查！`
                  )
                }
              }
            }
          }
        }
      })
    }

    return list
  }

  /**
   * 获取组件Render信息
   * @param vueIns                  vue 实例
   * @param structs                 Dom树
   * @param scopeData               插槽数据
   */
  protected getNodeEleRenderInfo(
    vueIns: BwVueIns,
    structs: Array<BwCustomComStructModel>,
    scopeData: Record<string, any>
  ): VNode[] {
    let nodes: VNode[] = []
    if (Array.isArray(structs)) {
      structs.forEach((cDom) => {
        const nodeList = this.getNodeEleComInfo(
          vueIns,
          cDom,
          hasCircularRef(scopeData) ? scopeData : cloneDeep(scopeData)
        )
        nodes = nodes.concat(nodeList)
      })
    } else {
      nodes.push(h('span', {}, '还未设计元素节点'))
    }
    return nodes
  }

  /**
   * 获取节点条件信息
   */
  protected getNodeEleIfInfo(
    vueIns: BwVueIns,
    nodeIfInfo: BwCustomComNodeIfInfoModel | undefined,
    scopeData: Record<string, any>
  ) {
    const info: Record<string, any> = {
      visible: !!nodeIfInfo?.visible,
      value: false,
    }

    if (info.visible) {
      switch (nodeIfInfo?.conditionVarFrom) {
        case BwIfFromTypeEnum.constValue: {
          const constValue = nodeIfInfo.constValue
          // @ts-ignore
          info.value = [true, 'true'].includes(constValue) ? true : false
          break
        }
        case BwIfFromTypeEnum.comVar: {
          const varName = nodeIfInfo.comVarName ?? ''
          info.value = !!vueIns[varName]
          break
        }
        case BwIfFromTypeEnum.comProps: {
          const comProps = vueIns.$props ?? {}
          const name = nodeIfInfo.comPropsName ?? ''
          info.value = !!comProps[name]
          break
        }
        case BwIfFromTypeEnum.JSExpression: {
          let curJsLogic = nodeIfInfo.jsExpBodyLogic ?? ''
          curJsLogic = getTsToJs(curJsLogic)
          curJsLogic = curJsLogic.replace('export default', 'return ')
          if (curJsLogic) {
            const fn = new Function('bwUtils', curJsLogic)

            const extndJSs = fn(vueIns)
            if (isFunction(extndJSs?.extnd)) {
              const params = getJSExpParams(
                vueIns,
                scopeData ?? {},
                nodeIfInfo.jsExpParams ?? []
              )

              info.value = extndJSs.extnd(params)
            }
          }
          break
        }
      }
    }

    return info
  }

  /**
   * 获取节点循环信息
   */
  protected getNodeEleForInfo(
    vueIns: BwVueIns,
    nodeForInfo: BwCustomComNodeForInfoModel | undefined,
    scopeData: Record<string, any>
  ) {
    const info: Record<string, any> = {
      visible: !!nodeForInfo?.visible,
      value: false,
    }

    if (info.visible) {
      switch (nodeForInfo?.forVarFrom) {
        case BwForFromTypeEnum.constValue: {
          const constValue = nodeForInfo.constValue
          // @ts-ignore
          info.value = [true, 'true'].includes(constValue) ? true : false
          break
        }
        case BwForFromTypeEnum.comVar: {
          const varName = nodeForInfo.comVarName ?? ''
          info.value = vueIns[varName]
          break
        }
        case BwForFromTypeEnum.comProps: {
          const comProps = vueIns.$props ?? {}
          const name = nodeForInfo.comPropsName ?? ''
          info.value = comProps[name]
          break
        }
        case BwForFromTypeEnum.JSExpression: {
          let curJsLogic = nodeForInfo.jsExpBodyLogic ?? ''
          curJsLogic = getTsToJs(curJsLogic)
          curJsLogic = curJsLogic.replace('export default', 'return ')
          if (curJsLogic) {
            const fn = new Function('bwUtils', curJsLogic)

            const extndJSs = fn(vueIns)
            if (isFunction(extndJSs?.extnd)) {
              const params = getJSExpParams(
                vueIns,
                scopeData ?? {},
                nodeForInfo.jsExpParams ?? []
              )

              info.value = extndJSs.extnd(params)
            }
          }
          break
        }
      }
    }

    return info
  }

  /** 获取插槽节点信息 */
  protected getSlotNodeInfo(
    vueIns: BwVueIns,
    nodeInfo: BwCustomComStructModel,
    scopeData: Record<string, any>
  ) {
    const nodeAtts = nodeInfo.attrs ?? {}
    const soltInfo: BwCustomComSoltNodeInfo = {
      slotName: 'default',
      slotParams: undefined,
    }
    Object.keys(nodeAtts).forEach((name) => {
      switch (name) {
        case 'name': {
          const info = nodeAtts[name]
          soltInfo.slotName = getComAttrValue(vueIns, info, scopeData)
          break
        }
        case 'params': {
          const info = nodeAtts[name]
          soltInfo.slotParams = getComAttrValue(vueIns, info, scopeData)
          break
        }
      }
    })

    return soltInfo
  }

  /**
   * 节点组件渲染
   * @param vueIns                  vue 实例
   * @param struct                  Dom信息
   * @param scopeData               插槽数据
   */
  protected getNodeEleComInfo(
    vueIns: BwVueIns,
    struct: BwCustomComStructModel,
    scopeData: Record<string, any>
  ): VNode[] {
    const nodes: Array<VNode> = []
    const curScopeData = scopeData
    const curInsSols = (vueIns?.$slots ?? {}) as Record<string, Function>
    const { nodeId, comType, comName, children, nodeIfInfo, nodeForInfo } =
      struct || {}
    const cusIfInfo = this.getNodeEleIfInfo(vueIns, nodeIfInfo, scopeData)
    const cusForInfo = this.getNodeEleForInfo(vueIns, nodeForInfo, scopeData)

    /** 判断当前节点是否是条件节点，且条件允许渲染 */
    if (!cusIfInfo.visible || cusIfInfo.value) {
      /** 判断当前节点是否是循环节点 */
      if (cusForInfo.visible) {
        const curForVar = cusForInfo.value
        if (Array.isArray(curForVar)) {
          this.nodeForVars[nodeId] = curForVar

          curScopeData.forData = {}
          curForVar.forEach((item, index) => {
            const curForData = { index, data: item }
            curScopeData.forData[nodeId] = curForData

            const insScopeData = hasCircularRef(curScopeData)
              ? curScopeData
              : cloneDeep(curScopeData)
            if (comType === BwComTypeEnum.vue) {
              switch (comName) {
                case BwVueComNameEnum.slot: {
                  const { slotName, slotParams } = this.getSlotNodeInfo(
                    vueIns,
                    struct,
                    insScopeData
                  )

                  if (isFunction(curInsSols[slotName])) {
                    const node = curInsSols[slotName](slotParams)
                    nodes.push(node)
                  }
                  break
                }
                case BwVueComNameEnum.component: {
                  const nodeComInfo = h(
                    markRaw(comEle),
                    {
                      vueIns,
                      ref: nodeId,
                      key: nodeId,
                      nodeInfo: struct,
                      frontType: this.frontType,
                      scopeData: curScopeData,
                    },
                    []
                  )
                  nodes.push(nodeComInfo)
                  break
                }
                default: {
                  break
                }
              }
            } else {
              const nodeComInfo = h(
                markRaw(desiginItem),
                getRenderComAttrs({
                  vueIns,
                  ref: nodeId,
                  key: nodeId,
                  nodeInfo: struct,
                  frontType: this.frontType,
                  scopeData: insScopeData,
                  getCustomComInfo: this.getCustomComInfo,
                  extendUtils: this.extendUtils,
                }),
                this.getChildNodeEleComInfo(
                  vueIns,
                  children ?? null,
                  insScopeData
                )
              )
              nodes.push(nodeComInfo)
            }
          })
        } else {
          const info = this.designInfo
          const message = `组件【${info?.comName}】循环节点数据错误，无法完成渲染，请检查！`

          createWarningModal({ content: message })
          throw new Error(message)
        }
      } else {
        if (comType === BwComTypeEnum.vue) {
          switch (comName) {
            case BwVueComNameEnum.slot: {
              const { slotName, slotParams } = this.getSlotNodeInfo(
                vueIns,
                struct,
                curScopeData
              )

              if (isFunction(curInsSols[slotName])) {
                const node = curInsSols[slotName](slotParams)
                nodes.push(node)
              }
              break
            }

            case BwVueComNameEnum.component: {
              const nodeComInfo = h(
                markRaw(comEle),
                {
                  vueIns,
                  ref: nodeId,
                  key: nodeId,
                  nodeInfo: struct,
                  frontType: this.frontType,
                  scopeData: curScopeData,
                },
                []
              )
              nodes.push(nodeComInfo)
              break
            }
            default: {
              break
            }
          }
        } else {
          const nodeComInfo = h(
            markRaw(desiginItem),
            getRenderComAttrs({
              vueIns,
              ref: nodeId,
              key: nodeId,
              nodeInfo: struct,
              frontType: this.frontType,
              getCustomComInfo: this.getCustomComInfo,
              extendUtils: this.extendUtils,
              scopeData: curScopeData,
            }),
            this.getChildNodeEleComInfo(vueIns, children ?? null, curScopeData)
          )
          nodes.push(nodeComInfo)
        }
      }
    } else {
      // console.log('节点组件渲染', struct)
    }

    return nodes
  }

  /**
   * 获取子节点
   * @param vueIns
   * @param comId
   * @param slotList
   * @param domNodes
   */
  protected getChildNodeEleComInfo(
    vueIns: BwVueIns,
    domNodes: Record<string, Array<BwCustomComStructModel>> | null,
    scopeData: Record<string, any>
  ): Record<string, BwChildrenNodeCallback> {
    const self = this
    const resData: Record<string, BwChildrenNodeCallback> = {}
    if (domNodes) {
      Object.keys(domNodes).forEach((slotName) => {
        const childNodes = domNodes ? domNodes[slotName] : []

        resData[slotName] = (slotInfos: any) => {
          const newScopeData = Object.assign({}, scopeData, slotInfos || {})
          let nodes: VNode[] = []
          if (childNodes && childNodes.length) {
            nodes = self.getNodeEleRenderInfo(
              vueIns,
              childNodes,
              hasCircularRef(newScopeData)
                ? newScopeData
                : cloneDeep(newScopeData)
            )
          }

          return nodes
        }
      })
    }
    return resData
  }

  /** 设置设计信息 */
  public setDesignInfo(info: BwCustomComDesignInfoModel) {
    this.curCustomCom = null
    this.designInfo = info
  }

  /** 获取自定义组件 */
  public setComCode(code: string) {
    this.curComCode = code
  }

  /** 获取自定义组件 */
  public getCom() {
    if (this.curCustomCom == null) {
      //@ts-ignore
      this.curCustomCom = this.createCom()
    }
    return this.curCustomCom
  }

  /** 更新元素节点 */
  public onUpdateNodeInfo(nodeId: string) {
    const refs = this.curVueIns?.$refs
    if (refs) {
      const insEle: any = refs[nodeId]
      const setNodeInfo = insEle?.setNodeInfo
      if (isFunction(setNodeInfo)) {
        setNodeInfo(nodeId)
      }
    }
  }

  /** 隐藏元素节点 */
  public onHideNodeEle(nodeIds: Array<string>) {
    const nodes = document.querySelectorAll(`.com-dom-node`)

    nodes.forEach((ele) => {
      const eleNodeId = ele.getAttribute('nodeid') ?? ''
      nodeIds.includes(eleNodeId)
        ? ele?.classList.add('dom-node-hide')
        : ele?.classList.remove('dom-node-hide')
    })
  }
}
